/*
 * id3_proc.c
 * This file is part of trypsyn-dsp
 *
 * Copyright (C) 2012 - Luke William Westby
 *
 * trypsyn-dsp is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * trypsyn-dsp is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with trypsyn-dsp. If not, see <http://www.gnu.org/licenses/>.
 */

#include "trypsyn_dsp.h"
#include "id3_defs.h"
#include "id3_proc.h"

void trypsyn_id3_swap32_ltb(trypsyn_uint32_t input, trypsyn_uint32_t *output) {
	
	*output = ((input>>24)&0xFF) | ((input<<8)&0xFF0000) |
			  ((input>>8)&0xFF00) | ((input<<24)&0xFF000000);
}

void trypsyn_id3_swap24_ltb(trypsyn_uint32_t input, trypsyn_uint32_t *output) {
	
	*output = ((input>>16)&0xFF) | ((input<<16)&0xFF0000) | ((input)&0x00FF00);
}

void trypsyn_id3_unsync(trypsyn_syncint32_t input, trypsyn_uint32_t *output) {

        *output = 0;
        trypsyn_uint32_t mask = 0x7F000000;
 
        while (mask) {
                *output >>= 1;
                *output |= input & mask;
                mask >>= 8;
        }
}

/* -------------------------------------------------------------------------- *
 * trypsyn_id3_read_header()
 * PAY ATTENTION: this and other read functions do not seek to the location
 * of what they attempt to read as it very well might not exist. therefore,
 * if you are using this function and not any of the broader top-level
 * functions which encapsulate it, make sure the file is properly sought.
 * if the id3v2 tag is at the start of the file, one use might be but is not
 * limited to
 *
 *     trypsyn_id3_header header;
 *     fseek(fp, 0, SEEK_SET);
 *     trypsyn_id3_read_header(&header, fp);
 *
 * additionally, the function simply assumes that an id3v2 tag exists. it is
 * meant to lie underneath more robust top-level parser functions.
 * -------------------------------------------------------------------------- */
 
int trypsyn_id3_read_header(trypsyn_id3_header *header, FILE *fp) {
	
	// read the first three bytes to get ID
	trypsyn_uint8_t identifier[4];
	if(fread(identifier, 1, 3, fp) < 0) 
		return TRYPSYN_ERROR_BADREAD;
	
	// null terminator
	identifier[3] = 0x00;
	
	// check for the presence of the tag
	if(strcmp(identifier, "ID3") != 0)
		return TRYPSYN_ERROR_NOID3;
	
	// seek to position 0
	fseek(fp, 0, SEEK_SET);
	
	// read the 10 header bytes
	if(fread(header->identifier, 1, 3, fp) < 0)
		return TRYPSYN_ERROR_BADREAD;
	
	header->identifier[3] = '\0';
	
	if(fread(&(header->major_version), 1, 1, fp) < 0)
		return TRYPSYN_ERROR_BADREAD;
	if(fread(&(header->revision), 1, 1, fp) < 0)
		return TRYPSYN_ERROR_BADREAD;
	if(fread(&(header->flags), 1, 1, fp) < 0)
		return TRYPSYN_ERROR_BADREAD;
	if(fread(&(header->sync_header_size), 4, 1, fp) < 0)
		return TRYPSYN_ERROR_BADREAD;
	
	// unsync size
	trypsyn_id3_swap32_ltb(header->sync_header_size, &header->sync_header_size);
	trypsyn_id3_unsync(header->sync_header_size, &(header->header_size));
	
	return TRYPSYN_ERROR_NONE;
}

int trypsyn_id3_read_frame_header_v2(trypsyn_id3_frame_header_v2 *frame_header, FILE *fp) {
	
	// read 
	if(fread(frame_header->identifier, 1, 3, fp) < 0)
		return TRYPSYN_ERROR_BADREAD;
	frame_header->identifier[3] = '\0';
	
	
	if(fread(&(frame_header->frame_size), 3, 1, fp) < 0)
		return TRYPSYN_ERROR_BADREAD;
	trypsyn_id3_swap24_ltb(frame_header->frame_size, &(frame_header->frame_size));
	
	return TRYPSYN_ERROR_NONE;
}
	
int trypsyn_id3_read_frame_header_v4(trypsyn_id3_frame_header_v4 *frame_header, FILE *fp) {

	trypsyn_syncint32_t ss;
	trypsyn_uint32_t us;

	if(fread(frame_header->identifier, 1, 4, fp) < 0)
		return TRYPSYN_ERROR_BADREAD;
	if(fread(&ss, 4, 1, fp) < 0)
		return TRYPSYN_ERROR_BADREAD;
	
	trypsyn_id3_swap32_ltb(ss, &ss);
	trypsyn_id3_unsync(ss, &us);
	frame_header->frame_size = us;
	
	if(fread(&(frame_header->flags), 1, 2, fp) < 0)
		return TRYPSYN_ERROR_BADREAD;

	return TRYPSYN_ERROR_NONE;
}

int trypsyn_id3_print_header(trypsyn_id3_header *header) {
	
	printf("Tag Version: %sv%d.%.2d\n",
		   header->identifier,
		   header->major_version,
		   header->revision);

	printf("Header Flags: %%%d%d%d%d0000\n",
		   header->flags && TRYPSYN_ID3_HEADER_FLAGS_UNSYNC,
		   header->flags && TRYPSYN_ID3_HEADER_FLAGS_XHEADER,
		   header->flags && TRYPSYN_ID3_HEADER_FLAGS_EXP,
		   header->flags && TRYPSYN_ID3_HEADER_FLAGS_FOOTER);
	
	printf("Tag Size: %d bytes\n", header->header_size);
}

int trypsyn_id3_print_frame_header_v2(trypsyn_id3_frame_header_v2 *frame_header,
									  int index) {

	printf("Frame %d ID: %s\n",
		   index,
		   frame_header->identifier);

	printf("Frame %d Size: %d bytes\n",
		   index,
		   frame_header->frame_size);
}

int trypsyn_id3_print_frame_header_v4(trypsyn_id3_frame_header_v4 *frame_header,
									  int index) {

	printf("Frame %d ID: %s\n",
		   index,
		   frame_header->identifier);

	printf("Frame %d Size: %d bytes\n",
		   index,
		   frame_header->frame_size);

	printf("Frame %d Flags: %%0%d%d%d0000 0%d00%d%d%d%d\n",
		   index,
		   frame_header->flags && TRYPSYN_ID3_FRAME_V4_TAGPRES,
		   frame_header->flags && TRYPSYN_ID3_FRAME_V4_FILEPRES,
		   frame_header->flags && TRYPSYN_ID3_FRAME_V4_RDONLY,
		   frame_header->flags && TRYPSYN_ID3_FRAME_V4_GROUPID,
		   frame_header->flags && TRYPSYN_ID3_FRAME_V4_COMPRESS,
		   frame_header->flags && TRYPSYN_ID3_FRAME_V4_ENCRYPT,
		   frame_header->flags && TRYPSYN_ID3_FRAME_V4_UNSYNC,
		   frame_header->flags && TRYPSYN_ID3_FRAME_V4_DATALEN);
}
